package com.hsogoo.ultraman.spi;

import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.hsogoo.ultraman.enums.ConfigChangeType;
import com.hsogoo.ultraman.event.ConfigChangeEvent;
import com.hsogoo.ultraman.listener.RepositoryChangeListener;
import com.hsogoo.ultraman.model.ConfigChange;
import com.hsogoo.ultraman.repository.ConfigRepository;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Created by za-huangsaigang on 2018/9/13.
 */
@Slf4j
public class DefaultConfig extends AbstractConfig implements RepositoryChangeListener {

    private final String namespace;
    private ConfigRepository configRepository;
    private Properties resourceProperties;
    private volatile AtomicReference<Properties> configProperties = new AtomicReference<>();

    public DefaultConfig(String namespace, ConfigRepository configRepository) {
        this.namespace = namespace;
        this.configRepository = configRepository;
        resourceProperties = loadResourceProperties(namespace);
        configProperties.set(configRepository.getConfig());
        configRepository.addChangeListener(this);
    }

    private Properties loadResourceProperties(String namespace) {
        String name = String.format("META-INF/config/%s.properties", namespace);
        InputStream in = getClass().getClassLoader().getResourceAsStream(name);
        Properties properties = null;
        if (in != null) {
            properties = new Properties();
            try {
                properties.load(in);
            } catch (IOException ex) {
                log.error("Load resource config for namespace {} failed", namespace, ex);
            } finally {
                try {
                    in.close();
                } catch (IOException ex) {
                    // ignore
                }
            }
        }
        return properties;
    }

    @Override
    public String getProperty(String key, String defaultValue) {
        // step 1: check system properties, i.e. -Dkey=value
        String value = System.getProperty(key);
        // step 2: check local cached properties file
        if (value == null && configProperties.get() != null) {
            value = configProperties.get().getProperty(key);
        }
        //step 3: check system env variable
        if (value == null) {
            value = System.getenv(key);
        }
        // step 4: check resource properties file from classpath
        if (value == null && resourceProperties != null) {
            value = (String) resourceProperties.get(key);
        }
        return value == null ? defaultValue : value;
    }

    @Override
    public Set<String> getPropertyNames() {
        if (configProperties.get() == null) {
            return Collections.EMPTY_SET;
        }
        return configProperties.get().stringPropertyNames();
    }

    //配置仓库的配置文件发生变化时，变更内存中的配置
    @Override
    public void onRepositoryChange(String namespace, Properties newProperties) {
        Properties oldProperties = configProperties.get();
        if (newProperties.equals(oldProperties)) {
            return;
        }
        log.info("{}配置仓库的配置文件发生变化时，变更内存中的配置", namespace);
        Properties newConfigProperties = new Properties();
        newConfigProperties.putAll(newProperties);

        //计算出需要修改的配置
        Map<String, ConfigChange> actualChanges = updateAndCalcConfigChanges(newConfigProperties);
        log.info("发生配更的配置项：[{}]", actualChanges);
        //check double checked result
        if (actualChanges.isEmpty()) {
            return;
        }
        //更新内存配置
        configProperties.set(newProperties);
        //触发配置项变更的监听，动态刷新
        fireConfigChange(new ConfigChangeEvent(namespace, actualChanges));
    }

    private Map<String, ConfigChange> updateAndCalcConfigChanges(Properties current) {
        log.info("计算配置变化的项。。。");
        if (configProperties.get() == null) {
            configProperties.set(new Properties());
        }
        Properties previous = configProperties.get();
        if (current == null) {
            current = new Properties();
        }

        Set<String> previousKeys = previous.stringPropertyNames();
        Set<String> currentKeys = current.stringPropertyNames();

        Set<String> commonKeys = Sets.intersection(previousKeys, currentKeys);
        Set<String> newKeys = Sets.difference(currentKeys, commonKeys);
        Set<String> removedKeys = Sets.difference(previousKeys, commonKeys);

        List<ConfigChange> changes = Lists.newArrayList();

        for (String newKey : newKeys) {
            changes.add(new ConfigChange(namespace, newKey, null, current.getProperty(newKey),
                    ConfigChangeType.ADDED));
        }

        for (String removedKey : removedKeys) {
            changes.add(new ConfigChange(namespace, removedKey, previous.getProperty(removedKey), null,
                    ConfigChangeType.DELETED));
        }

        for (String commonKey : commonKeys) {
            String previousValue = previous.getProperty(commonKey);
            String currentValue = current.getProperty(commonKey);
            if (Objects.equal(previousValue, currentValue)) {
                continue;
            }
            changes.add(new ConfigChange(namespace, commonKey, previousValue, currentValue,
                    ConfigChangeType.MODIFIED));
        }

        if(CollectionUtils.isNotEmpty(changes)){
            Map<String, ConfigChange> changedMap = Maps.uniqueIndex(changes, new Function<ConfigChange, String>() {
                @Override
                public String apply(ConfigChange change) {
                    return change.getPropertyName();
                }
            });
            return changedMap;
        }
        return Collections.EMPTY_MAP;
    }

}
